Write a custom CUDA kernel to optimize the Fake Quantize operation based on a specific formula.

The mathematical definition provided is:
output = (clamp(round(input / scale) + zero_point, quant_min, quant_max) - zero_point) * scale

Inputs:
- input: Float32 Tensor.
- scale: Scalar Float32.
- zero_point: Scalar Int32.
- quant_min, quant_max: Scalar Int32.

Problem Analysis:
This operation simulates quantization error. It involves element-wise division, rounding, addition, clamping, subtraction, and multiplication. Doing this naively involves high memory bandwidth usage.

Optimization Strategy:
1.  **Fused Kernel**: Perform the entire logic in a single CUDA kernel pass.
2.  **Specific Rounding Logic**: Implement `round(input/scale) + zero_point` (rounding before adding zero_point) as requested. Use `rintf` for "round to nearest even".
3.  **Vectorized Access**: Use `float4` loads/stores to process 4 elements per thread, maximizing memory bandwidth.
4.  **Scalar Optimization**: Load `scale` and `zero_point` once per thread/block from global memory and keep them in registers.
  
Here's an example to show you the syntax of inline embedding custom CUDA operators in torch: The example given architecture is:   
  
```python
import torch
import torch.nn as nn

BATCH_SIZE = 2048
DIM = 2048
SHAPE = (BATCH_SIZE, DIM)

QMIN = -128
QMAX = 127

class FakeQuantize(nn.Module):
    """
    Fake Quantize Per Tensor Affine
    """
    def __init__(self, quant_min=QMIN, quant_max=QMAX):
        super(FakeQuantize, self).__init__()
        self.quant_min = quant_min
        self.quant_max = quant_max

    def forward(self, x, scale, zero_point):
        return torch.fake_quantize_per_tensor_affine(
            x, scale, zero_point, self.quant_min, self.quant_max
        )

class Model(nn.Module):
    def __init__(self):
        super(Model, self).__init__()
        self.fq = FakeQuantize()
    
    def forward(self, x, scale, zero_point):
        return self.fq(x, scale, zero_point)

def get_inputs():
    x = torch.randn(SHAPE, dtype=torch.float32)
    scale = torch.tensor([0.05], dtype=torch.float32)
    zero_point = torch.tensor([10], dtype=torch.int32)
    
    return [x.contiguous(), scale, zero_point]

def get_init_inputs():
    return []